package com.mxx.common.data.mongo;

import com.mxx.common.utils.BsonUtil;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @Author itzgyw
 * @Date 2020-02-10
 * @Time 12:30
 * @Description:
 */
public class MongoHelper {

    private List<Criteria> criteriaList;
    private Document fieldDocument;

    private Criteria cacheCriteria;

    private MongoHelper(List<Criteria> criteriaList) {
        this.fieldDocument = new Document();
        this.criteriaList = criteriaList;
    }

    public static MongoHelper instance() {
        return new MongoHelper(new ArrayList<>());
    }

    public MongoHelper equal(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).is(val));
        return this;
    }

    public MongoHelper rightLike(String column, String val) {
        if (column == null || val == null) {
            return this;
        }
        Pattern pattern = Pattern.compile(String.format("^%s%s$", val.trim(), ".*"));
        criteriaList.add(Criteria.where(column).is(pattern));
        return this;
    }

    public MongoHelper leftLike(String column, String val) {
        if (column == null || val == null) {
            return this;
        }
        Pattern pattern = Pattern.compile(String.format("^%s%s$", ".*", val.trim()));
        criteriaList.add(Criteria.where(column).is(pattern));
        return this;
    }

    public MongoHelper like(String column, String val) {
        if (column == null || val == null) {
            return this;
        }
        Pattern pattern = Pattern.compile(String.format("^%s%s%s$", ".*", val.trim(), ".*"));
        criteriaList.add(Criteria.where(column).is(pattern));
        return this;
    }

    public MongoHelper exists(String column) {
        if (column == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).exists(true));
        return this;
    }

    public MongoHelper notExists(String column) {
        if (column == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).exists(false));
        return this;
    }

    public MongoHelper lessThan(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).lt(val));
        return this;
    }

    public MongoHelper greaterThan(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).gt(val));
        return this;
    }

    public MongoHelper greaterThanAndEqual(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).gte(val));
        return this;
    }

    public MongoHelper lessThanAndEqual(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).lte(val));
        return this;
    }

    public MongoHelper in(String column, Collection val) {
        if (column == null || val == null || val.size() == 0) {
            return this;
        }
        criteriaList.add(Criteria.where(column).in(val));
        return this;
    }

    public MongoHelper notIn(String column, Collection val) {
        if (column == null || val == null || val.size() == 0) {
            return this;
        }
        criteriaList.add(Criteria.where(column).in(val).not());
        return this;
    }

    public MongoHelper notEqual(String column, Object val) {
        if (column == null || val == null) {
            return this;
        }
        criteriaList.add(Criteria.where(column).ne(val));
        return this;
    }

    public MongoHelper display(String... columns) {
        BsonUtil.display(fieldDocument, columns);
        return this;
    }

    public MongoHelper display(Class classes) {
        BsonUtil.display(fieldDocument, classes);
        return this;
    }

    public MongoHelper noDisplay(String... columns) {
        BsonUtil.noDisplay(fieldDocument, columns);
        return this;
    }

    public MongoHelper noDisplay(Class classes) {
        BsonUtil.noDisplay(fieldDocument, classes);
        return this;
    }

    public MongoHelper and() {
        if (criteriaList.isEmpty() || criteriaList.size() == 1) {
            return this;
        }
        Criteria criteria = criteriaList.remove(0);
        criteria.andOperator(criteriaList.toArray(new Criteria[criteriaList.size()]));
        criteriaList.clear();
        criteriaList.add(criteria);
        return this;
    }

    public MongoHelper or() {
        if (criteriaList.isEmpty() || criteriaList.size() == 1) {
            return this;
        }
        Criteria criteria = criteriaList.remove(0);
        criteria.orOperator(criteriaList.toArray(new Criteria[criteriaList.size()]));
        criteriaList.clear();
        criteriaList.add(criteria);
        return this;
    }

    public MongoHelper cacheOr() {
        if (this.criteriaList.size() > 1) {
            throw new RuntimeException("Cannot perform pre storage if condition is greater than one");
        }
        if (criteriaList.isEmpty()) {
            return this;
        }
        if (cacheCriteria == null) {
            this.cacheCriteria = criteriaList.remove(0);
        } else {
            this.cacheCriteria.orOperator(criteriaList.toArray(new Criteria[criteriaList.size()]));
            this.criteriaList.clear();
        }
        return this;
    }

    public MongoHelper cacheAnd() {
        if (this.criteriaList.size() > 1) {
            throw new RuntimeException("Cannot perform pre storage if condition is greater than one");
        }
        if (criteriaList.isEmpty()) {
            return this;
        }
        if (cacheCriteria == null) {
            this.cacheCriteria = criteriaList.remove(0);
        } else {
            this.cacheCriteria.andOperator(criteriaList.toArray(new Criteria[criteriaList.size()]));
            this.criteriaList.clear();
        }
        return this;
    }

    private Query generateQuery() {
        if (fieldDocument.isEmpty()) {
            return new Query();
        }

        return new BasicQuery(new Document(), fieldDocument);
    }

    public Query endAnd() {
        this.cacheAnd();
        if (cacheCriteria != null) {
            return generateQuery().addCriteria(cacheCriteria);
        } else {
            return generateQuery();
        }
    }

    public Criteria getCriteria() {
        return cacheCriteria;
    }

    public Query endOr() {
        this.cacheOr();
        if (cacheCriteria != null) {
            return generateQuery().addCriteria(cacheCriteria);
        } else {
            return generateQuery();
        }
    }
}
